这就是event文档,,你们可以感受一下。从文档上看,event扩展一共实现了如下图几个基础类,其中最常用重要的就是Event和EventBase以及EventConfig三个类了,所以,先围绕这三位开展一下工作。

    考虑到你们、我、还有正在看这个文章的其他未知物种,大多数可能并不是搞C语言的老兵油子,所以我得用一些可能并不恰当的案例和比喻来尝试引入这些概念。

    libevent中有五个字母是event,实际上就是说“event才是王道”。

    Event类就是产生各种不同类型事件的产出器,比如定时器事件、读写事件等等,为了提升民族荣誉感,我们将这些各种事件比作各种战斗机:比如歼10、歼15和歼20。

    13. PHP socket初探 —- 硬着头皮继续libevent(二) - 图1

    EventBase类就相对容易介入了,这玩意显然就是一个航空母舰了,为了提升民族荣誉感,我们就把EventBase类当作是辽宁舰。各种Event都必须依靠EventBase才能混口饭吃,这和战斗机有辽宁舰才有底气飞的更高更远是一个道理。一定是先有航母(EventBase),其次是战斗机(Event)挂在航母(EventBase)上。

    下面我们从开始写一个php定时器来步入到代码的节奏中。定时器是大家常用的一个工具,一般phper一说定时器,脑海中第一个想起的绝逼是Linux中的crontab。难道phper们离开了crontab真的就没法混了吗?是的,真的好羞耻,现实告诉我们就是这样的,他们离开了crontab真的就没法混了。那么,是时候通过纯php来搞一波儿定时器实现了!

    注意是真的纯php,连Event扩展都不用的那种。

    代码保存成timer.php,然后php timer.php运行下,如果不出问题应该能跑起来。但是吧,这个代码有一坨问题。

    • 首先是性能一般( 但是,比使用declare(ticks=1)还是要好不少的 )
    • 其次是代码量确实短小,短小的都让人怀疑:这特么玩意能用?
    • 最后是即便我硬着头皮用,但这玩意只能精确到秒级,逗我?

    所以,为了解决以上问题,是时候操作一波儿Event扩展了!

    1. // 初始化一个EventConfig(舰岛),虽然是个仅用于演示的空配置
    2. $eventConfig = new EventConfig();
    3. // 根据EventConfig初始化一个EventBase(辽宁舰,根据舰岛配置下辽宁舰)
    4. $eventBase = new EventBase( $eventConfig );
    5. // 初始化一个定时器event(歼15,然后放到辽宁舰机库中)
    6. $timer = new Event( $eventBase, -1, Event::TIMEOUT | Event::PERSIST, function(){
    7. echo microtime( true )." : 歼15,滑跃,起飞!".PHP_EOL;
    8. } );
    9. // tick间隔为0.05秒钟,我们还可以改成0.5秒钟甚至0.001秒,也就是毫秒级定时器
    10. $tick = 0.05;
    11. // 将定时器event添加(将歼15拖到甲板加上弹射器)
    12. $timer->add( $tick );
    13. // eventBase进入loop状态(辽宁舰!走你!)

    将代码保存为tick.php,然后php tick.php执行一下,如下图所示:13. PHP socket初探 —- 硬着头皮继续libevent(二) - 图2

    这种定时器是持久的定时器(每隔X时间一定会执行一次),如果想要一次性的定时器(隔X时间后就会执行一次,执行过后再也不执行了),那么将上述代码中的“Event::TIMEOUT | Event::PERSIST”修改为“Event::TIMEOUT”即可。

    如果你有一些自定义用户数据传递给回调函数,可以利用new Event()的第五个参数,这五个参数可以给回调函数用,如下所示:

    1. <?php
    2. $timer = new Event( $eventBase, -1, Event::TIMEOUT | Event::PERSIST, function() use( &$custom ){
    3. //echo microtime( true )." : 歼15,滑跃,起飞!".PHP_EOL;
    4. print_r( $custom );
    5. }, $custom = array(
    6. 'name' => 'woshishui',
    7. ) );

    需要重点说明的是new Event()这行代码了,我把原型贴过来给大家看下:

    • 第一个参数是一个eventBase对象即可
    • 第二个参数是文件描述符,可以是一个监听socket、一个连接socket、一个fopen打开的文件或者stream流等。如果是时钟时间,则传入-1。如果是其他信号事件,用相应的信号常量即可,比如SIGHUP、SIGTERM等等
    • 第三个参数表示事件类型,依次是Event::READ、Event::WRITE、Event::SIGNAL、Event::TIMEOUT。其中,加上Event::PERSIST则表示是持久发生,而不是只发生一次就再也没反应了。比如Event::READ | Event::PERSIST就表示某个文件描述第一次可读的时候发生一次,后面如果又可读就绪了那么还会继续发生一次。
    • 第四个参数就熟悉的很了,就是事件回调了,意思就是当某个事件发生后那么应该具体做什么相应
    • 第五个参数是自定义数据,这个数据会传递给第四个参数的回调函数,回调函数中可以用这个数据。
    1. 创建EventConfig(非必需)
    2. 创建EventBase
    3. 创建Event
    4. 将Event挂起,也就是执行了Event对象的add方法,不执行add方法那么这个event对象就无法挂起,也就不会执行
    5. 将EventBase执行进入循环中,也就是loop方法

    捋清楚了定时器代码,我们尝试来解决一个信号的问题。比如我们的进程是常驻内存的daemon,再接收到某个信号后就会作出相应的动作,比如收到term信号后进程就会退出、收到usr1信号就会执行reload等等。

    1. // 依然是照例行事,尽管暂时没什么实际意义上的配置
    2. $eventConfig = new EventConfig();
    3. // 初始化eventBase
    4. $eventBase = new EventBase( $eventConfig );
    5. // 初始化event
    6. echo "signal term.".PHP_EOL;
    7. } );
    8. // 挂起event对象
    9. $event->add();
    10. // 进入循环
    11. echo "进入循环".PHP_EOL;
    12. $eventBase->loop();

    将代码保存成tick.php,然后执行php tick.php,代码已经进入循环了,然后我们打开另外一个终端,输入ps aux|grep tick查看一个php进程的pid进程号,对这个进程发送term信号,如下图所示:

    13. PHP socket初探 —- 硬着头皮继续libevent(二) - 图3

    奇怪啊,从第一张图看到确实收到term信号了,但是很奇怪为什么这个php进程退出了呢?是因为没有添加Event::PERSIST,修改如下代码如下:

    1. <?php
    2. $event = new Event( $eventBase, SIGTERM, Event::SIGNAL | Event::PERSIST, function(){
    3. echo "signal term.".PHP_EOL;
    4. } );

    可能还有一些同学觉得php进程退出就退出,收到kill就退出,好正常,你仔细看下,tick.php进程只要求收到一个term就echo输出,并没有说规定进程退出,而且使用kill要看发送什么信号的,kill -15,这个参数通过kill -l查看多种信号值,-15是属于SIGTERM

    有些心眼多鸡贼的,IO多路复用的方法一共有三个select、poll和epoll(Mac下叫做kqueue),那么我们当前的event扩展用的是哪个方法呢?那么,再表演一波儿:

    <?php
    // 查看当前系统平台支持的IO多路复用的方法都有哪些?
    $method = Event::getSupportedMethods();
    print_r( $method );
    // 查看当前用的方法是哪一个?
    $eventBase = new EventBase();
    echo "当前event的方法是:".$eventBase->getMethod().PHP_EOL;
    // 跑了许久龙套的config这次也得真的露露手脚了
    $eventConfig = new EventConfig;
    // 避免使用方法kqueue
    $eventConfig->avoidMethod('kqueue');
    // 利用config初始化event base
    $eventBase = new EventBase( $eventConfig );
    echo "当前event的方法是:".$eventBase->getMethod().PHP_EOL;
    

    将代码保存了,然后执行一下,可以看到结果如下图所示:

    <?php
    $base = new EventBase();
    echo "特性:".PHP_EOL;
    $features = $base->getFeatures();
    // 看不到这个判断条件的,请反思自己“位运算”相关欠缺
    if( $features & EventConfig::FEATURE_ET ){
      echo "边缘触发".PHP_EOL;
    }
    if( $features & EventConfig::FEATURE_O1 ){
      echo "O1添加删除事件".PHP_EOL;
    }
    if( $features & EventConfig::FEATURE_FDS ){
      echo "任意文件描述符,不光socket".PHP_EOL;
    }
    

    运行结果如下图所示:13. PHP socket初探 —- 硬着头皮继续libevent(二) - 图4